package edu.pku.sei.transformation.atl.engine;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.m2m.atl.core.ATLCoreException;
import org.eclipse.m2m.atl.core.IExtractor;
import org.eclipse.m2m.atl.core.IInjector;
import org.eclipse.m2m.atl.core.IModel;
import org.eclipse.m2m.atl.core.IReferenceModel;
import org.eclipse.m2m.atl.core.ModelFactory;
import org.eclipse.m2m.atl.core.emf.EMFExtractor;
import org.eclipse.m2m.atl.core.emf.EMFInjector;
import org.eclipse.m2m.atl.core.emf.EMFModelFactory;
import org.eclipse.m2m.atl.core.launch.ILauncher;
import org.eclipse.m2m.atl.core.service.CoreService;
import org.eclipse.m2m.atl.engine.compiler.AtlDefaultCompiler;
import org.eclipse.m2m.atl.engine.compiler.atl2006.Atl2006Compiler;
import org.eclipse.m2m.atl.engine.emfvm.launch.EMFVMLauncher;
import org.eclipse.m2m.atl.engine.parser.AtlParser;
import org.eclipse.swt.widgets.Display;

import edu.pku.sei.transformation.engine.IEngine;


public class ATLRunner implements IEngine{
	private final static String ATL_FORMAT_NAME = "EMF";
	private final static String FILE_PREFIX = "file:/";
	
	private ILauncher launcher;
	private IExtractor extractor;
	private IInjector injector;
	private ModelFactory factory;
	
	private Map<String, String> metaModelPathMap = new HashMap<String, String>();
	private Map<String, String> modelTypeMap = new HashMap<String, String>();
	private Map<String, String> modelPathMap = new HashMap<String, String>();
	private Map<String, IReferenceModel> metaModelMap = new HashMap<String, IReferenceModel>();
	
	private String targetModelName = "";
	private IModel targetModel = null;
	private String targetModelPath = "";
	
	protected String rulePath = "";
	protected String asmPath = "";
	
	static {
		CoreService.registerLauncher(EMFVMLauncher.LAUNCHER_NAME, EMFVMLauncher.class);
		CoreService.registerFactory(ATL_FORMAT_NAME, EMFModelFactory.class); 
		CoreService.registerExtractor(ATL_FORMAT_NAME, EMFExtractor.class);
		CoreService.registerInjector(ATL_FORMAT_NAME, EMFInjector.class);
	}
	
	public boolean launch(){
		try {
			FileInputStream asmStream = new FileInputStream(asmPath);
			launcher.launch(ILauncher.RUN_MODE, new NullProgressMonitor(), Collections.<String, Object> emptyMap(), asmStream);
			asmStream.close();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return true;
	}

	public boolean init() {
		try {
			injector = CoreService.getInjector(ATL_FORMAT_NAME);
			extractor = CoreService.getExtractor(ATL_FORMAT_NAME);
			factory = CoreService.getModelFactory(ATL_FORMAT_NAME);
			
			// Getting launcher
			launcher = CoreService.getLauncher(EMFVMLauncher.LAUNCHER_NAME);
			launcher.initialize(Collections.<String, Object> emptyMap());
			
			IReferenceModel refiningTraceMetamodel = factory.getBuiltInResource("RefiningTrace.ecore");
			IModel refiningTraceModel = factory.newModel(refiningTraceMetamodel);
			launcher.addOutModel(refiningTraceModel, "refiningTrace", "RefiningTrace");
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
		
		return true;
	}

	private void loadModel() {
		for (Entry<String, String> entry : modelPathMap.entrySet()) {
			String modelName = entry.getKey();
			String modelPath = entry.getValue();
			String metaModelName = modelTypeMap.get(modelName);
			
			//load model
			IModel model = null;
			try {
				IReferenceModel metaModel = metaModelMap.get(metaModelName);
				model = factory.newModel(metaModel);
				File modelFile = new File(modelPath);
				if (!modelFile.exists()) {
					createEmptyModelFile(modelFile, metaModelPathMap.get(metaModelName));
				}
				injector.inject(model, FILE_PREFIX+modelPath);
				
			} catch (Exception e) {
				e.printStackTrace();
			}
			
			if(modelName.equals(targetModelName)){
				launcher.addInOutModel(model, modelName, metaModelName);
				targetModel = model;
				targetModelPath = modelPath;
			}
			else 
				launcher.addInModel(model, modelName, metaModelName);
		}
	}
	
	private void loadMetaModel () {
		for (Entry<String, String> e : metaModelPathMap.entrySet()) {
			try {
				IReferenceModel metaModel = factory.newReferenceModel();
				injector.inject(metaModel, FILE_PREFIX+e.getValue());
				metaModelMap.put(e.getKey(), metaModel);
			} catch (ATLCoreException e1) {
				e1.printStackTrace();
			}
		}
	}
	
	private boolean saveModel() {
		if(targetModel != null){
			try {
				extractor.extract(targetModel, FILE_PREFIX +targetModelPath);
			} catch (ATLCoreException e) {
				e.printStackTrace();
				return false;
			}
			return true;
		}
		return false;
	}
	
	
	public void registModel(String modelName, String modelPath, String metaModelName, String metaModelPath) {
		modelPathMap.put(modelName, modelPath);
		modelTypeMap.put(modelName, metaModelName);
		metaModelPathMap.put(metaModelName, metaModelPath);
	}
	
	public void setRuleFile(String ruleFile) {
		this.rulePath = ruleFile;
	}
	
	public void setTargetModel(String modelName) {
		this.targetModelName = modelName;
	}
	
	public void setAsmPath(String path) {
		this.asmPath = path;
	}

	@Override
	public int execute(final IProgressMonitor monitor) {
		
		init();
		loadMetaModel();
		loadModel();
		compile();
		launch();
		saveModel();
		Display.getDefault().asyncExec(new Runnable() {
			@Override
			public void run() {
				monitor.worked(1);	
			}
		});
		return 0;
	}
	
	private void createEmptyModelFile(File file, String metaModelPath) {
		String content1 = "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n<xmi:XMI xmi:version=\"2.0\" xmlns:xmi=\"http://www.omg.org/XMI\" xmlns:elmLst=\"";
		String content2 = "\">\n</xmi:XMI>";
		try {
			if (file.createNewFile()) {
				String path = metaModelPath.replace('\\', '/');
				String content = content1 + FILE_PREFIX + path + content2;
				FileOutputStream fs = new FileOutputStream(file);
				fs.write(content.getBytes());
				fs.close();
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	private void compile() {
		try {
			File ruleFile = new File(rulePath);
			FileInputStream ruleStream = new FileInputStream(ruleFile);
			
			File asmFile= new File(asmPath);
			asmFile.createNewFile();
			FileOutputStream asmStream = new FileOutputStream(asmFile);
			
			//parse
			AtlParser parser = AtlParser.getDefault();
			IModel target = parser.parseToModel(ruleStream);
			
			//compile
			AtlDefaultCompiler compiler = new Atl2006Compiler();
			compiler.compileWithProblemModel(target, asmStream);
			
			asmStream.close();
			ruleStream.close();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (ATLCoreException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}
